package com.only4play.usercenter.utils;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.only4play.common.utils.MD5Utils;
import com.only4play.usercenter.config.GatewayConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;


/**
 * @author liyuncong
 * @version 1.0
 * @file RequestSignatureProvider
 * @brief RequestSignatureProvider
 * @details RequestSignatureProvider
 * @date 2024-02-01
 *
 * Edit History
 * ----------------------------------------------------------------------------
 * DATE                     NAME               DESCRIPTION
 * 2024-02-01               liyuncong          Created
 */

@Slf4j
@Component
public class RequestSignatureProvider {

    public static final String TIMESTAMP = "timestamp";
    public static final String NONCE = "nonce";
    public static final String SIGNATURE = "signature";
    public static final String APP_ID = "appId";
    public static final String ACCESS_SECRET = "secret";
    // 接口超时时间
    public static final Integer TIMEOUT = 120;
    private static final String REDIS_KEY_PREFIX = "request_repeat_";
    private static final Integer REDIS_KEY_TIMEOUT = 60;

    @Autowired
    private GatewayConfiguration configuration;
    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 接口签名
     *
     * @param parameterMap 请求参数
     * @param secret       密钥
     * @return 签名
     */
    public String signature(Map<String, String> parameterMap, String secret) {
        // 删除secret
        parameterMap.remove(ACCESS_SECRET);

        String[] keys = parameterMap.keySet().toArray(new String[0]);
        Arrays.sort(keys);
        StringBuilder temp = new StringBuilder();
        boolean first = true;
        for (String key : keys) {
            if (first) {
                first = false;
            } else {
                temp.append("&");
            }
            temp.append(key).append("=");
            String value = parameterMap.getOrDefault(key, null);
            String valueString = "";
            if (null != value) {
                valueString = value;
            }
            temp.append(valueString);
        }
        temp.append("&").append(ACCESS_SECRET).append("=").append(secret);
        log.info("need signature str: {}", temp);
        return MD5Utils.encrypt(temp.toString()).toUpperCase();
    }

    /**
     * 校验签名
     *
     * @param requestHeadersMap 请求头
     * @param requestParamsMap  请求参数
     * @param requestBodyMap    请求body
     */
    public void validate(
        Map<String, Object> requestHeadersMap,
        Map<String, Object> requestParamsMap,
        Map<String, Object> requestBodyMap
    ) {
        // 是否开启接口签名校验
        if (!configuration.getSignature().isEnable()) {
            return;
        }

        // 获取签名
        String signature = (String) requestHeadersMap.getOrDefault(SIGNATURE, "");

        // 组装需签名参数
        Map<String, String> parameterMap = new HashMap<>(16);
        requestParamsMap.forEach((k, v) -> {
            //空值或空字符串不验签
            if (isBaseType(v) && StringUtils.isNotBlank(String.valueOf(v))) {
                parameterMap.put(k, String.valueOf(v));
            }
        });
        requestBodyMap.forEach((k, v) -> {
            //空值或空字符串不验签
            if (isBaseType(v) && StringUtils.isNotBlank(String.valueOf(v))) {
                parameterMap.put(k, String.valueOf(v));
            }
        });

        validate(parameterMap, signature);
    }

    /**
     * 校验签名
     *
     * @param parameterMap 需校验参数
     * @param signature    请求签名
     */
    public void validate(Map<String, String> parameterMap, String signature) {
        // 获取签名参数
        if (StringUtils.isBlank(signature)) {
            throw new RuntimeException("签名不能为空");
        }

        // 获取AppId
        String appId = parameterMap.getOrDefault(APP_ID, null);
        if (StringUtils.isBlank(appId)) {
            throw new RuntimeException("AppId不能为空");
        }

        // 获取随机字符串
        String nonce = parameterMap.getOrDefault(NONCE, null);
        if (StringUtils.isBlank(nonce)) {
            throw new RuntimeException("随机字符串不能为空");
        }

        // 获取时间戳
        String timestampStr = parameterMap.getOrDefault(TIMESTAMP, null);
        if (StringUtils.isBlank(timestampStr)) {
            throw new RuntimeException("时间戳不能为空");
        }

        // 是否使用客服端时间戳校验
        boolean useTimestamp = configuration.getSignature().isUseTimestamp();
        if (useTimestamp) {
            // 校验接口请求时间戳
            // 请求传过来的时间戳与服务器当前时间戳差值大于120，则当前请求的timestamp无效
            long requestTimestamp = Long.parseLong(timestampStr);
            boolean isTimeout = Instant.now().minusSeconds(TIMEOUT).isAfter(Instant.ofEpochMilli(requestTimestamp));
            if (isTimeout) {
                throw new RuntimeException("签名已过期");
            }
        }

        // 请求传过来的随机数如果在redis中存在，则当前请求的nonce无效
        String signKey = REDIS_KEY_PREFIX + nonce + "_" + signature;

        Object object = redisTemplate.boundValueOps(signKey).get();
        if (Objects.nonNull(object)) {
            throw new RuntimeException("随机字符串已存在");
        }

        //密钥配置
        Map<String, String> appKeys = configuration.getSignature().getAppKeys();

        // 根据AppId获取加密密钥
        String secret = appKeys.getOrDefault(appId, null);
        if (StringUtils.isBlank(secret)) {
            throw new RuntimeException("签名密钥未配置");
        }

        // 根据请求传过来的参数构造签名，如果和接口的签名不一致，则请求参数被篡改
        HashMap<String, String> signMap = new HashMap<>(parameterMap);
        String currentSign = signature(signMap, secret);
        if (!signature.equalsIgnoreCase(currentSign)) {
            throw new RuntimeException("签名不匹配");
        }

        // 存入redis，超时时间为60秒
        redisTemplate.boundValueOps(signKey).set(signature, REDIS_KEY_TIMEOUT, TimeUnit.SECONDS);
    }

    private static boolean isBaseType(Object o) {
        return o instanceof String
            || o instanceof Integer
            || o instanceof Double
            || o instanceof Float
            || o instanceof Long;
    }
}
